package com.connector.base.protocol;

import com.alibaba.fastjson.JSONObject;
import com.connector.base.exception.UnKnownCommandException;
import com.google.common.base.Charsets;
import io.netty.buffer.ByteBuf;

import java.beans.ConstructorProperties;
import java.util.Arrays;

public class Packet
{
    public static final int HEADER_LEN = 10;
    public static final byte NORMAL_REQ_TYPE = 0;
    public static final byte NORMAL_RESP_TYPE = 1;
    public static final byte CLUSTER_REQ_TYPE = 2;
    public static final byte CLUSTER_RESP_TYPE = 4;
    public static final Packet HEARTBEAT_REQ_PACKET;
    public static final Packet HEARTBEAT_RESP_PAFKET;
    private int bodyLength;
    private byte cmd;
    private byte type;
    private int sessionId;
    private byte flags;
    private byte[] body;

    public Packet(final byte cmd) {
        this.cmd = cmd;
        this.sessionId = 0;
        this.type = 0;
        this.bodyLength = 0;
        this.flags = 0;
    }

    public Packet(final byte cmd, final byte type) {
        this.cmd = cmd;
        this.type = type;
        this.bodyLength = 0;
        this.flags = 0;
    }

    public Packet(final byte cmd, final byte type, final int sessionId) {
        this.cmd = cmd;
        this.type = type;
        this.sessionId = sessionId;
        this.bodyLength = 0;
        this.flags = 0;
    }

    public Packet(final byte cmd, final byte type, final int sessionId, final int bodyLength) {
        this.cmd = cmd;
        this.type = type;
        this.sessionId = sessionId;
        this.bodyLength = bodyLength;
        this.flags = 0;
    }

    public boolean hasFlag(final byte flag) {
        return (this.flags & flag) != 0x0;
    }

    public void addFlag(final byte flag) {
        this.flags |= flag;
    }

    public boolean isReq() {
        return (this.type | 0x0) == 0x0 || (this.type | 0x0) == 0x2;
    }

    public boolean isResp() {
        return (this.type | 0x0) == 0x1 || (this.type | 0x0) == 0x4;
    }

    public boolean isClusterReq() {
        return (this.type | 0x0) == 0x2;
    }

    public boolean isClusterResp() {
        return (this.type | 0x0) == 0x4;
    }

    public void convertPacketToClusterReq() {
        if (this.isReq() && !this.isClusterReq()) {
            this.setType((byte)2);
        }
    }

    public void convertPacketToClusterResp() {
        if (this.isResp() && !this.isClusterResp()) {
            this.setType((byte)4);
        }
    }

    public JSONObject getTargetBody() {
        return JSONObject.parseObject(new String(this.getBody(), Charsets.UTF_8));
    }

    public void setTargetBody(final JSONObject body) {
        this.setBody(body.toJSONString().getBytes(Charsets.UTF_8));
    }

    public static Packet decodePacket(final Packet packet, final ByteBuf in, final int bodyLength) {
        packet.setBodyLength(bodyLength);
        packet.setCmd(in.readByte());
        packet.setType(in.readByte());
        packet.setSessionId(in.readInt());
        packet.setFlags(in.readByte());
        if (packet.getBodyLength() > 0) {
            in.readBytes(packet.getBodyLength());
        }
        return packet;
    }

    public static void encodePacket(final Packet packet, final ByteBuf out) {
        if (packet.getCmd() == Command.UNKNOWN.cmd) {
            throw new UnKnownCommandException("connector receive unknown command");
        }
        out.writeInt(packet.getBodyLength());
        out.writeByte((int)packet.getCmd());
        out.writeByte((int)packet.getType());
        out.writeInt(packet.getSessionId());
        out.writeByte((int)packet.getFlags());
        if (packet.getBodyLength() > 0) {
            out.writeBytes(packet.getBody());
        }
    }

    public Packet response(final Command command) {
        return new Packet(command.cmd, (byte)1, this.sessionId);
    }

    public int getBodyLength() {
        return this.bodyLength;
    }

    public byte getCmd() {
        return this.cmd;
    }

    public byte getType() {
        return this.type;
    }

    public int getSessionId() {
        return this.sessionId;
    }

    public byte getFlags() {
        return this.flags;
    }

    public byte[] getBody() {
        return this.body;
    }

    public void setBodyLength(final int bodyLength) {
        this.bodyLength = bodyLength;
    }

    public void setCmd(final byte cmd) {
        this.cmd = cmd;
    }

    public void setType(final byte type) {
        this.type = type;
    }

    public void setSessionId(final int sessionId) {
        this.sessionId = sessionId;
    }

    public void setFlags(final byte flags) {
        this.flags = flags;
    }

    public void setBody(final byte[] body) {
        this.body = body;
    }

    @ConstructorProperties({ "bodyLength", "cmd", "type", "sessionId", "flags", "body" })
    public Packet(final int bodyLength, final byte cmd, final byte type, final int sessionId, final byte flags, final byte[] body) {
        this.bodyLength = bodyLength;
        this.cmd = cmd;
        this.type = type;
        this.sessionId = sessionId;
        this.flags = flags;
        this.body = body;
    }

    @Override
    public String toString() {
        return "Packet(bodyLength=" + this.getBodyLength() + ", cmd=" + this.getCmd() + ", type=" + this.getType() + ", sessionId=" + this.getSessionId() + ", flags=" + this.getFlags() + ", body=" + Arrays.toString(this.getBody()) + ")";
    }

    public Packet() {
    }

    static {
        HEARTBEAT_REQ_PACKET = new Packet(Command.HEARTBEAT.cmd);
        HEARTBEAT_RESP_PAFKET = new Packet(Command.HEARTBEAT.cmd, (byte)1);
    }
}
